/**
 * @file   Array.cpp
 * @author Djuro Drljaca (djurodrljaca@gmail.com)
 * @date   2014-04-20
 * @brief  DON data type object: Array
 *
 * Copyright 2014  Djuro Drljaca (djurodrljaca@gmail.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <DON/Core/DataType/Array.h>
#include <DON/Core/DataType/UInt16.h>
#include <QtCore/QByteArray>
#include <QtCore/QList>

using namespace DON::Core::DataType;

class Array::Private
{
public:
    Private()
        : itemDataType(DataType_Invalid),
          itemList(QList<const AbstractDataType *>())
    { }

    ~Private()
    {
        removeAllItems();
    }

    bool isIndexInRange(const int index) const;
    void removeAllItems();

    DataType itemDataType;
    QList<const AbstractDataType *> itemList;
};

Array::Array()
    : AbstractDataType(),
      d(new Private())
{
}

Array::Array(const Array &other)
    : AbstractDataType(),
      d(new Private())
{
    d->itemDataType = other.d->itemDataType;

    const int size = other.d->itemList.size();

    for (int index = 0; index < size; ++index)
    {
        const AbstractDataType *item = other.d->itemList.at(index);

        if (item != NULL)
        {
            d->itemList.append(item->clone());
        }
    }
}

Array::~Array()
{
    delete d;
}

Array & Array::operator=(const Array &other)
{
    d->itemDataType = other.d->itemDataType;

    d->removeAllItems();

    const int size = other.d->itemList.size();

    for (int index = 0; index < size; ++index)
    {
        const AbstractDataType *item = other.d->itemList.at(index);

        if (item != NULL)
        {
            d->itemList.append(item->clone());
        }
    }

    return *this;
}

AbstractDataType * Array::clone() const
{
    return new Array(*this);
}

DataType Array::getDataType() const
{
    return DataType_Array;
}

DataType Array::getItemDataType() const
{
    return d->itemDataType;
}

bool Array::setItemDataType(const DataType dataType)
{
    bool success = false;

    if (isDataTypeValid(dataType))
    {
        success = true;

        if (d->itemDataType != dataType)
        {
            d->itemDataType = dataType;
            d->removeAllItems();
        }
    }

    return success;
}

int Array::getItemCount() const
{
    return d->itemList.size();
}

const AbstractDataType * Array::getItem(const int index) const
{
    const AbstractDataType *item = NULL;

    if (d->isIndexInRange(index))
    {
        item = d->itemList.at(index);
    }

    return item;
}

bool Array::setItem(const AbstractDataType &item, const int index)
{
    bool success = false;

    if ((item.getDataType() == d->itemDataType) &&
        (d->isIndexInRange(index)))
    {
        delete d->itemList[index];
        d->itemList[index] = item.clone();
        success = true;
    }

    return success;
}

bool Array::addItem(const AbstractDataType &item)
{
    bool success = false;

    if ((item.getDataType() == d->itemDataType) &&
        (d->itemList.size() < (1 << 16)))
    {
        d->itemList.append(item.clone());
        success = true;
    }

    return success;
}

bool Array::removeItem(const int index)
{
    bool success = false;

    if (d->isIndexInRange(index))
    {
        delete d->itemList.takeAt(index);
        success = true;
    }

    return success;
}

void Array::removeAllItems()
{
    d->removeAllItems();
}

bool Array::fromBinary(const QByteArray &data, const int startIndex, int *size)
{
    bool success = false;
    QList<const AbstractDataType *> itemList;
    int processedDataSize = 0;

    // Parse the array, minimum of 3 bytes is expected
    if ((data.size() > 0) &&
        (startIndex >= 0) &&
        ((startIndex + 3) <= data.size()))
    {
        // Item Count
        const int itemCount = static_cast<int>(UInt16::fromBinary(data, startIndex, &success));

        // Data Type
        DataType itemDataType = DataType_Invalid;

        if (success)
        {
            const int itemDataTypeIndex = startIndex + 2;
            itemDataType = DataType::fromBinaryToDataType(data.at(itemDataTypeIndex));

            if (itemDataType == DataType_Invalid)
            {
                success = false;
            }
        }

        // Item List
        if (success)
        {
            // Add item count and data type sizes to the processed data size
            processedDataSize = 3;

            // Calculate start index of the first item
            int itemDataIndex = startIndex + 3;

            // Parse all items
            for (int itemIndex = 0; itemIndex < itemCount; ++itemIndex)
            {
                // Create an instance of an item with the array's item data type
                AbstractDataType *item = AbstractDataType::createInstance(itemDataType);

                if (item != NULL)
                {
                    // Parse item
                    int size = 0;
                    success = item->fromBinary(data, itemDataIndex, &size);

                    if (success)
                    {
                        // Add item to the array
                        itemList.append(item);
                        item = NULL;

                        // Calculate start index of the next item
                        itemDataIndex += size;

                        // Add the item size to the processed data size
                        processedDataSize += size;
                    }
                    else
                    {
                        // In case of an error delete the item
                        delete item;
                        item = NULL;
                    }
                }
                else
                {
                    // Error, NULL pointer
                    success = false;
                }

                if (success == false)
                {
                    // Exit loop in case of an error
                    break;
                }
            }
        }
    }

    if (success)
    {
        d->itemList = itemList;

        if (size != NULL)
        {
            *size = processedDataSize;
        }
    }

    return success;
}

QByteArray Array::toBinary() const
{
    QByteArray data;

    if (isDataTypeValid(d->itemDataType))
    {
        // Item Count
        const int size = d->itemList.size();

        data.append(UInt16::toBinary(static_cast<quint16>(size)));

        // Data Type
        data.append(DataType::fromDataTypeToBinary(d->itemDataType));

        // Item List
        for (int itemIndex = 0; itemIndex < size; ++itemIndex)
        {
            const AbstractDataType *item = d->itemList.at(itemIndex);
            const QByteArray itemData = item->toBinary();

            if (itemData.isEmpty() == false)
            {
                data.append(itemData);
            }
            else
            {
                data.clear();
                break;
            }
        }
    }

    return data;
}

Array * Array::fromAbstractDataType(AbstractDataType *instance)
{
    Array *outputInstance = NULL;

    if (instance != NULL)
    {
        if (instance->getDataType() == DataType_Array)
        {
            outputInstance = static_cast<Array *>(instance);
        }
    }

    return outputInstance;
}

bool Array::Private::isIndexInRange(const int index) const
{
    bool inRange = false;

    if ((index >= 0) &&
        (index < this->itemList.size()))
    {
        inRange = true;
    }

    return inRange;
}

void Array::Private::removeAllItems()
{
    const int size = itemList.size();

    if (size > 0)
    {
        for (int index = 0; index < size; ++index)
        {
            delete itemList[index];
        }

        itemList.clear();
    }
}
